package utils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import com.google.common.collect.Lists;

public class RaidControllerLab3 {

	private MixRaidInfo info;
	private String root;
	public RaidControllerLab3(String root) {
		initController(root);
		this.root = root;
	}
	private void initController(String root2) {
		info = RaidUtilsLab3.getRaidInfo(root2);
		if(info == null) {
			System.out.println("can't extract info from " + root2);
			System.exit(1);
		} 
	}
	public void writeFile(int i, File file) {
		
		switch (info.getType()) {
		case RAID01: {
			writeFile01(i, file);
			break;
		}
		case RAID10: {
			writeFile10(i, file);
			break;
		}
		default:
			break;
		}
	}
	
	public void readFile(int i, File file) {
		
		switch (info.getType()) {
		case RAID01: {
			readFile01(i, file);
			break;
		}
		case RAID10: {
			readFile10(i, file);
			break;
		}
		default:
			break;
		}
	}
	public void rebuild() {
		switch (info.getType()) {
		case RAID01: {
			rebuild01();
			break;
		}
		case RAID10: {
			rebuild10();
			break;
		}
		default:
			break;
		}

	}
	private void rebuild10() {
		String prepareRoot = BlockUtils.prepareRoot(root);
		int diskCount = (info.getDiskCount() / 2);
		List<File> files  = null;
		for(int i = 0; i < diskCount; i++) {
			files = Lists.newArrayList();
			File f = new File(prepareRoot + "raid"+i);
			if(f.exists() && f.canRead()) {
				files.add(f);
				for(int j = diskCount; j < info.getDiskCount(); j++) {
					if(j % diskCount == i) {
						File f1 = new File(prepareRoot + "raid"+j);
						if(f1.exists() && f1.canRead()) {
							files.add(f1);
						} else {
							files = null;
							break;
						}
					}
				}
				if(files != null)
					break;
			}
		}
		if(files == null || files.isEmpty()) {
			System.out.println("can't rebuild raid array because there is no single unbroken disk");
			System.exit(1);
		}
		List<byte[]> bytes = Lists.newArrayList();
		for(File file : files) {
			FileInputStream fin;
			try {
				fin = new FileInputStream(file);
				byte[] result = new byte[(int) file.length()];
				fin.read(result);
				fin.close();
				bytes.add(result);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		for(int i = 0; i < info.getDiskCount(); i++) {
			File f = new File(prepareRoot + "raid"+i);
			if(f.exists() && f.canRead()) {
				continue;
			} else {
				if(!f.exists())
					try {
						f.createNewFile();
					} catch (IOException e1) {
						e1.printStackTrace();
					}
				if(!f.canWrite() || !f.canRead()) {
					System.out.println(f.getName() + " is unavalaible");
					System.exit(1);
				}
				int offset = i  / diskCount;
				try {
					FileOutputStream fout = new FileOutputStream(f);
					fout.write(bytes.get(offset));
					fout.close();
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	private void rebuild01() {
		int diskCount = (int) (info.getDiskCount() / 2);
		String prepareRoot = BlockUtils.prepareRoot(root);
		for(int i = 0; i < info.getDiskCount(); i++) {
			File f = new File(prepareRoot + "raid"+i);
			if(f.exists() && f.canRead()) {
				continue;
			} else {
				for(int j = 0; j < info.getDiskCount(); j++) {
					if(i == j)
						continue;
					if(j % diskCount == i) {
						File f1 = new File(prepareRoot + "raid"+j);
						if(f1.exists() && f1.canRead()) {
								FileInputStream fin;
								try {
									fin = new FileInputStream(f1);
									byte[] result = new byte[(int) f1.length()];
									fin.read(result);
									FileOutputStream fout = new FileOutputStream(f);
									fout.write(result);
									fin.close();
									fout.close();
								} catch (FileNotFoundException e) {
									e.printStackTrace();
								} catch (IOException e) {
									e.printStackTrace();
								}
							} else {
								System.out.println("this raid array can't rebuild because 1 array strip doesn't exist");
								System.exit(1);
							}
						} 
					}
				}
			}
	}
	private void readFile10(int blockNumber, File file) {
		int diskCount = (int) (info.getDiskCount() / 2);
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		for(int k = 0; k < info.getDiskCount(); k++) {
			if(k % diskCount == 0) {
				try {
					byte[] readFromRaid = RaidUtils.readFromRaid(root, k, blockNumber);
					stream.write(readFromRaid, 0, readFromRaid.length);
				} catch (FileNotFoundException e) {
					System.out.println("disk " + k + " doesn't exist");
					System.out.println("try to rebuild");
					return;
				} catch (IOException e) {
					System.out.println("I/O exception on disk " + k);
					System.out.println("try to rebuild");
					return;
				}
			}
		}
		RaidUtils.writeToFile(file, stream);
	}
	private void readFile01(int blockNumber, File file) {
		int diskCount = (int) (info.getDiskCount() / 2);
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		for(int i = 0; i < diskCount; i++) {
			try {
				byte[] readFromRaid = RaidUtils.readFromRaid(root, i, blockNumber);
				stream.write(readFromRaid, 0, readFromRaid.length);
			} catch (FileNotFoundException e) {
				System.out.println("disk " + i + " doesn't exist");
				System.out.println("try to rebuild");
				return;
			} catch (IOException e) {
				System.out.println("I/O exception on disk " + i);
				System.out.println("try to rebuild");
				return;
			}
		}
		RaidUtils.writeToFile(file, stream);
	}
	private void writeFile10(int blockNumber, File file) {
		int diskCount = (int) (info.getDiskCount() / 2);
		List<byte[]> sliceFile = RaidUtils.sliceFile(file, 2);
		for(int i = 0; i < diskCount; i++) {
			int offset = 0;
			for(int k = 0; k < info.getDiskCount(); k++) {
				if(k % diskCount == i) {
					RaidUtils.writeToRaid(root, k, blockNumber, sliceFile.get(offset));
					offset++;
				}
			}
		}
	}
	private void writeFile01(int blockNumber, File file) {
		int diskCount = (int) (info.getDiskCount() / 2);
		List<byte[]> sliceFile = RaidUtils.sliceFile(file, diskCount);
		for(int i = 0; i < info.getDiskCount(); i++) {
			RaidUtils.writeToRaid(root, i, blockNumber, sliceFile.get(i % diskCount));
		}
		
	}
	public MixRaidInfo getInfo() {
		return info;
	}

}
